package square;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;
import be.kuleuven.cs.som.annotate.Value;


/**
 * A class of temperature values involving a numeral and a temperature scale.
 * 
 * @invar	The numeral of each temperature must be a valid numeral
 * 			| isValidTemperatureValue(getTemperatureValue())
 * @invar	The temperature scale of each temperature must be a valid temperature scale.
 * 			| isValidTemperatureScale(getTemperatureScale())
 * 
 * @version	2.0
 * @author 	Inge Gielis <inge.gielis@student.kuleuven.be>
 * @author 	Annelies Ketelslegers <annelies.ketelslegers@student.kuleuven.be>
 */

@Value
public class Temperature implements Comparable<Temperature>{

	/**
	 * Initialize this new temperature with given numeral and given temperature scale.
	 * 
	 * @param 	numeral
	 * 			The numeral for this new temperature.
	 * @param	temperatureScale
	 * 			The temperature scale for this new temperature.
	 * @post 	The numeral of this new temperature is equal to
	 * 			the given numeral.
	 * 			| new.getTemperatureValue() == numeral
	 * @post 	The temperature scale for this new temperature
	 * 			is equal to the given scale.
	 * 			| new.getTemperatureScale() == temperatureScale
	 * @throws	IllegalArgumentException
	 * 			The given numeral is not a valid value for the temperature.
	 * 			| ! isValidTemperatureValue(numeral, temperatureScale)
	 * @throws  IllegalArgumentException
	 * 			The given temperature scale is not effective.
	 * 			| (temperatureScale == null)
	 */
	@Raw
	public Temperature(double numeral, TemperatureScale temperatureScale)
			throws IllegalArgumentException{
		if (temperatureScale == null)
			throw new IllegalArgumentException("Invalid temperatureScale");
		if(! isValidTemperatureValue(numeral,temperatureScale))
			throw new IllegalArgumentException("Invalid temperatureValue: "+numeral+" "+temperatureScale);
		this.temperatureScale = temperatureScale;
		this.temperatureValue = numeral;
		
	}
	
	/**
	 * Initialize this new temperature with given numeral and temperature scale CELCIUS.
	 * 
	 * @param 	numeral
	 * 			The numeral for this new temperature.
	 * @effect  This new temperature is initialized with the given numeral and
	 * 			the temperature scale CELCIUS.
	 * 			| this(numeral, TemperatureScale.CELCIUS)
	 * @throws	IllegalArgumentException
	 * 			The given numeral is not a valid value for the temperature.
	 * 			| ! isValidTemperatureValue(numeral, TemperatureScale.CELSIUS)
	 */
	@Raw
	public Temperature(double numeral) throws IllegalArgumentException{
		this(numeral, TemperatureScale.CELCIUS);
	}
	
	/**
	 * Return the temperatureScale of this temperature
	 */
	@Basic @Raw @Immutable
	public TemperatureScale getTemperatureScale(){
		return this.temperatureScale;
	}
	
	/**
	 * Check whether the given temperature scale is a valid temperature scale
	 * for any temperature.
	 * 
	 * @param 	temperatureScale 
	 * 			The temperature scale to check.
	 * @return	True if and only if the given temperature scale is effective
	 * 			| result == (temperatureScale != null)
	 */
	public static boolean isValidTemperatureScale(TemperatureScale temperatureScale){
		return temperatureScale != null;
	}
	
	/**
	 * Variable referencing the temperature scale for this temperature.
	 */
	private TemperatureScale temperatureScale;
	
	/**
	 * Return the numeral of this temperature
	 */
	@Basic @Raw @Immutable
	public double getTemperatureValue(){
		return this.temperatureValue;
	}
	
	/**
	 * Check whether the given numeral is a valid numeral for any temperature object.
	 * 
	 * @param 	numeral
	 * 			The numeral to check
	 * @param 	temperatureScale
	 * 			The scale for which the numeral must be checked.
	 * @return 	True if the numeral is not "not a number" and not an infinite value and if the numeral is not below 0 Kelvin,
	 * 			false otherwise.
	 * 			|if (! (Double.isNaN(numeral)) && ! (Double.isInfinite(numeral)) 
	 * 			|		&& temperatureScale.convertTo(numeral, TemperatureScale.KELVIN) >= 0)
	 *			|	then result == true
	 */
	public static boolean isValidTemperatureValue(double numeral,
			TemperatureScale temperatureScale) {
		if (!(Double.isNaN(numeral)) && !(Double.isInfinite(numeral))
				&& temperatureScale.convertTo(numeral, TemperatureScale.KELVIN) >= 0)
			return true;
		return false;
	}
	
	/**
	 * Variable referencing the numeral of this temperature.
	 */
	private double temperatureValue;
	
	/**
	 * Return a temperature that has the same value as this temperature 
	 * expressed in the given scale.
	 * 
	 * @param 	newScale
	 * 			The temperature scale to convert to.
	 * @return 	The resulting temperature is effective.
	 * 			| result != null
	 * @return	The resulting temperature has the given temperature scale
	 * 			as its temperature scale.
	 * 			| result.getTemperatureScale() == newScale
	 * @throws	IllegalArgumentException
	 * 			The given temperature scale is not effective.
	 * 			| ! isValidTemperatureScale(newScale)
	 */
	public Temperature convertTo(TemperatureScale newScale){
		if(! isValidTemperatureScale(newScale))
			throw new IllegalArgumentException("The given temperaturescale was not effective.");
		return new Temperature(this.getTemperatureScale().convertTo(this.getTemperatureValue(), newScale), newScale);
	}
	
	/**
	 * Compare this temperature to another temperature.
	 * 
	 * @param	other
	 * 			The other temperature to compare with.
	 * @return	The result is equal to the comparison of the value of this temperature with the value 
	 * 			of the other temperature converted to the temperature scale of this temperature.
	 * 			|result ==
	 * 			|	(new Double(this.getTemperatureValue())).compareTo(new Double(other.convertTo(this.getTemperatureScale()).getTemperatureValue()))
	 * @throws	ClassCastException
	 * 			The other temperature is not effective.
	 * 			| other == null
	 */
	@Override
	public int compareTo(Temperature other) {
		if(other == null)
			throw new ClassCastException("Non-effective temperature");
		return (new Double(this.getTemperatureValue())).compareTo(new Double(other.convertTo(this.getTemperatureScale()).getTemperatureValue()));
	}
	
	/**
	 * Check whether this temperature is equal to the given object.
	 * 
	 * @return	True if and only if the given object is effective, 
	 * 			and if this temperature and the given object belong to the same class,
	 * 			and if this temperature and the other object interpreted as a temperature 
	 * 			have equal numerical values and have the same temperaturescale.
	 * 			Because of possible loss of precision, this method checks to a precision of
	 * 			0.0001 instead of equal numbers.
	 * 			| result ==
	 * 			|	(( other != null)
	 * 			|	&& (this.getClass() == other.getClass())
	 * 			|	&& ((this.getTemperatureScale() == otherTemperature.getTemperatureScale())
					&& (this.getTemperatureValue()-otherTemperature.getTemperatureValue() < 0.0001))
	 * 
	 */
	@Override
	public boolean equals(Object other){
		if(other ==  null)
			return false;
		if(this.getClass() != other.getClass())
			return false;
		Temperature otherTemperature = (Temperature) other;
		return ((this.getTemperatureScale() == otherTemperature.getTemperatureScale())
					&& (this.getTemperatureValue()-otherTemperature.getTemperatureValue() < 0.0001));
	}
	
	/**
	 * Return the hash code for this temperature. 
	 */
	@Override
	public int hashCode(){
		int result=0;
		switch (this.getTemperatureScale()){
		case CELCIUS:
			result = 10000;
		case FAHRENHEIT:
			result = 20000;
		case KELVIN:
			result = 30000;
		}
		return result +(new Double(this.getTemperatureValue())).intValue() ;
	}
	
	/**
	 * Return a textual representation of this temperature.
	 * 
	 * @return	A string consisting of the textual representation of the numeral of this temperature,
	 * 			followed by the textual representation of the scale for this temperature.
	 * 			| result.equals("Temperature: "+getTemperatureValue()+" "+getTemperatureScale())	
	 */
	@Override
	public String toString(){
		String returnString = "";
		
		returnString += "Temperature: "+getTemperatureValue()+" "+getTemperatureScale();
		return returnString;
	}
	
	
	

}
